///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.controls
{
  import flash.events.Event;
  
  import ilog.gauges.circular.CircularLinearScale;
  import ilog.gauges.circular.renderers.CircularTrackRenderer;
  import ilog.gauges.gaugesClasses.LinearScale;
  import ilog.gauges.gaugesClasses.circular.CircularValueRenderer;
  import ilog.gauges.gaugesClasses.circular.ICircularNumericScale;
  import ilog.gauges.gaugesClasses.circular.SingleCircularGauge;
  import ilog.utils.CSSUtil;
  
  import mx.styles.CSSStyleDeclaration;
  
  [Exclude(name="scales", kind="property")]
  [Exclude(name="elements", kind="property")]      

  /**
   * Controls the visibility of the scale labels.
   *  
   * @default true
   */
  [Style(name="showLabels", type="Boolean", inherit="no")]

  /**
   * Controls the visibility of the scale major ticks.
   *  
   * @default true
   */
  [Style(name="showMajorTicks", type="Boolean", inherit="no")]

  /**
   * Controls the visibility of the scale minor ticks.
   *  
   * @default true
   */
  [Style(name="showMinorTicks", type="Boolean", inherit="no")]

  /**
   * Specifies whether only minimum and maximum labels are shown. 
   *   
   * @default false
   */
  [Style(name="onlyMinMaxLabels", type="Boolean", inherit="no")]
  
  /**
   * A style name to be used for the major tick renderers. Useful
   * for tick renderers that might use properties from this
   * style name.
   * 
   * @default null
   * @since ILOG Elixir 2.0
   */
  [Style(name="majorTickStyleName", type="String", inherit="no")]

  /**
   * A style name to be used for the minor tick renderers. Useful
   * for tick renderers that might use properties from this
   * style name.
   *
   * @default null 
   * @since ILOG Elixir 2.0   
   */
  [Style(name="minorTickStyleName", type="String", inherit="no")]      
 
  /**
   * This is a base class for circular gauges that renders a single
   * value on a single numeric scale.
   * This class defines a circular gauge with the following elements:
   * <ul>
   *   <li>a scale (angle range and value range are customizable)</li>
   *   <li>a value renderer (typically, a needle)</li> 
   *   <li>a title label, under the needle pivot</li>
   *   <li>a label, which shows the current gauge's value</li> 
   * </ul>
   *
   * <p>When a given property is available on <code>NumericCircularGauge</code>,
   * it overrides any corresponding property that may have been set on one of its predefined
   * elements. For example, the <code>minimum</code> property value of the 
   * <code>NumericCircularGauge</code> overrides the <code>minimum</code>
   * property value of the <code>ICircularNumericScale</code> referenced
   * by the <code>scale</code> property.</p>
   * 
   * <p>You do not use the <code>NumericCircularGauge</code> class 
   * directly, even if it is possible. Instead, you use one of its subclasses, such as 
   * <code>SimpleCircularGauge</code>, or you create your own subclass.</p>
   *  
   * @mxml
   *  
   * <p>The <code>&lt;ilog:NumericCircularGauge&gt;</code> tag inherits all the properties
   * of its parent classes and adds the following properties:</p>
   *  
   * <pre>
   * &lt;ilog:NumericCircularGauge
   *   <strong>Properties</strong>
   *   minimum="0"
   *   maximum="100"
   *   trackMinimum="50"
   *   trackMaximum="100"
   *   startAngle="210"
   *   endAngle="330"
   *   orientation="clockwise|cclockwise"
   *   majorTickInterval="NaN"
   *   minorTickInterval="NaN"
   *   snapInterval="1"
   *   value="<i>Number, No default</i>"
   *   scale="<i>A default linear scale</i>"
   * 
   *   <strong>Styles</strong>
   *   showLabels="true|false"
   *   showMajorTicks="true|false"
   *   showMinorTicks="true|false" 
   *   onlyMinMaxLabels="false|true"
   *   majorTickStyleName="null"
   *   minorTickStyleName="null"
   * /&gt;
   * </pre> 
   */  
  public class NumericCircularGauge extends SingleCircularGauge
  {
    /**
     * @private 
     */     
    private static var stylesInited:Boolean = initStyles(); 

    /**
     * @private 
     */ 
    private static function initStyles():Boolean
    {
      var style:CSSStyleDeclaration =
        CSSUtil.createSelector("NumericCircularGauge");   
            
      style.defaultFactory = function():void {
        this.showMajorTicks = true;
        this.showMinorTicks = true;
        this.onlyMinMaxLabels = false;
        this.showLabels = true;
        this.majorTickStyleName = null;
        this.minorTickStyleName = null;
      }
      return true;
    }
    
    /**
     * Constructor 
     */ 
    public function NumericCircularGauge()
    {
      super();
      scale = new CircularLinearScale();
      LinearScale(scale).snapInterval = 1;
    }
    
    private var _elementsDirty:Boolean = false;

    private var _scaleParam:Object = {
      minimum: 0,
      maximum: 100,
      majorTickInterval: NaN,
      minorTickInterval: NaN,
      startAngle: 210,
      endAngle: 330,
      orientation: "clockwise",
      snapInterval: 1,
      dirty: false
    };
    
    private var _trackParam:Object = {
      minimum: 50,
      maximum: 100,
      dirty: false
    };
    
    [Inspectable(category="Gauge")]
    
    /**
     * Specifies the increment to be used to snap values on this
     * scale during user interaction. For example, if <code>snapInterval</code> 
     * is 2, the <code>minimum</code> value is 0 and the <code>maximum</code> 
     * value is 10. The possible values returned by the scale for 
     * interactions are: 0, 2, 4, 6, 8, and 10.
     * A value of <code>NaN</code> means that no snapping takes place.
     * <strong>Warning:</strong> This property is only used if <code>scale</code> is a subclass of
     * <code>LinearScale</code>. 
     *
     * @default 1
     */
     public function get snapInterval():Number
     {
      return _scaleParam.snapInterval;
    }

    /**
     * @private 
     */
    public function set snapInterval(value:Number):void
    {
      _scaleParam.snapInterval = value;
      _scaleParam.dirty = true;
      invalidateProperties();
    }
    
    [Bindable]
    [Inspectable(category="Gauge")]
    
    /**
     * The logical scale of the gauge.
     * @default ilog.gauges.circular.CircularLinearScale 
     */ 
    public function get scale():ICircularNumericScale
    {
      return scales.length > 0 ? scales[0] : null;
    }
    
    /**
     * @private 
     */     
    public function set scale(value:ICircularNumericScale):void 
    {
      scales = [value];
    }

    private var _value:Number = NaN;

    [Bindable("change")]
    [Bindable("valueCommit")]
    [Inspectable(category="Gauge", defaultValue="null")]   
    
    /**
     * The current value of the gauge, pointed by the value renderer. 
     * 
     * @default NaN
     */       
    public function get value():Number
    {
      return _value;
    }

    /**
     * @private 
     */   
    public function set value(val:Number):void
    {
      _value = val;
      if (valueRenderer != null) {
        valueRenderer.value = val;
      }
    }

    /**
     * @private 
     */     
    public override function set valueRenderer(renderer:CircularValueRenderer):void
    {
      // store latest value in internal buffer
      _value = value;
      if (valueRenderer != null) {
        renderer.removeEventListener("change", changeListener);
        renderer.removeEventListener("valueCommit", changeListener);
      }
      if (renderer != null) {
        renderer.addEventListener("change", changeListener);
        renderer.addEventListener("valueCommit", changeListener);
      }                      
      super.valueRenderer = renderer;
      if (valueRenderer != null) {
        // make sure the old value is put back on the new renderer, without animation
        // for now
        var duration:Number = valueRenderer.getStyle("animationDuration");
        if (!isNaN(duration) && duration != 0) 
          valueRenderer.setStyle("animationDuration", 0);
        valueRenderer.value = _value;
        if (!isNaN(duration) && duration != 0)      
          valueRenderer.setStyle("animationDuration", duration);
      }
    }    
    
    [Inspectable(category="Gauge", defaultValue="0")]
    
    /**
     * The minimum value of the scale.
     * 
     * @default 0 
     */       
    public function get minimum():Number
    {
      return _scaleParam.minimum;
    }

    /**
     * @private 
     */
    public function set minimum(value:Number):void
    {
      _scaleParam.minimum = value;
      _scaleParam.dirty = true;
      invalidateProperties();
    }
    
    [Inspectable(category="Gauge", defaultValue="100")]
        
    /**
     * The maximum value of the scale.
     * 
     * @default 100 
     */       
    public function get maximum():Number
    {
      return _scaleParam.maximum;
    }

    /**
     * @private 
     */
    public function set maximum(value:Number):void
    {
      _scaleParam.maximum = value;
      _scaleParam.dirty = true;
      invalidateProperties();
    }
    
    [Inspectable(category="Gauge", defaultValue="210")]

    /**
     * The start angle of the scale, in degrees.
     * 
     * @default 210 
     */       
    public function get startAngle():Number
    {
      return _scaleParam.startAngle;
    }

    /**
     * @private 
     */
    public function set startAngle(value:Number):void
    {
      _scaleParam.startAngle = value;
      _scaleParam.dirty = true;
      invalidateProperties();
    }
    
    [Inspectable(category="Gauge", defaultValue="330")] 
       
    /**
     * The end angle of the scale, in degrees.
     * 
     * @default 330
     */ 
    public function get endAngle():Number
    {
      return _scaleParam.endAngle;
    }

    /**
     * @private 
     */
    public function set endAngle(value:Number):void
    {
      _scaleParam.endAngle = value;
      _scaleParam.dirty = true;
      invalidateProperties();
    }

    [Inspectable(category="Gauge")]
           
    /**
     * The interval between two major ticks. If <code>NaN</code>, the major
     * tick interval is automatically computed. The result depends on the type
     * of the scale.
     * 
     * @default NaN
     * 
     * @see ilog.gauges.gaugesClasses.LinearScale#majorTickInterval
     * @see ilog.gauges.gaugesClasses.LogScale#majorTickInterval
     */     
    public function get majorTickInterval():Number
    {
      return _scaleParam.majorTickInterval;
    }

    /**
     * @private 
     */
    public function set majorTickInterval(value:Number):void
    {
      _scaleParam.majorTickInterval = value;
      _scaleParam.dirty = true;
      invalidateProperties();
    }  

    [Inspectable(category="Gauge")]       
    
    /**
     * The interval between two minor ticks. If <code>NaN</code>, the minor
     * tick interval is automatically computed. This property is only supported if
     * the gauge scale is a <code>LinearScale</code> subclass.
     * 
     * @see #majorTickInterval
     * 
     * @default NaN
     * 
     * @see ilog.gauges.gaugesClasses.LinearScale#minorTickInterval
     */     
    public function get minorTickInterval():Number
    {      
      return _scaleParam.minorTickInterval;
    }

    /**
     * @private 
     */
    public function set minorTickInterval(value:Number):void
    {
      _scaleParam.minorTickInterval = value;
      _scaleParam.dirty = true;
      invalidateProperties();
    }  

    /**
     * @private
     */
    override public function set track(value:CircularTrackRenderer):void
    {
      super.track = value;
      _trackParam.dirty = true;
      invalidateProperties();
    }

    [Bindable]
    [Inspectable(category="Gauge", defaultValue="50")]
    
    /**
     * The minimum value of the track.
     * 
     * @default 50 
     */       
    public function get trackMinimum():Number
    {
      return _trackParam.minimum;
    }

    /**
     * @private 
     */   
    public function set trackMinimum(val:Number):void
    {
      _trackParam.minimum = val;
      _trackParam.dirty = true;
      invalidateProperties();
    }

    [Bindable]
    [Inspectable(category="Gauge", defaultValue="100")]
    
    /**
     * The maximum value of the track.
     *  
     * @default 100
     */       
    public function get trackMaximum():Number
    {
      return _trackParam.maximum;
    }

    /**
     * @private 
     */
    public function set trackMaximum(val:Number):void
    {
      _trackParam.maximum = val;
      _trackParam.dirty = true;
      invalidateProperties();
    }

    [Inspectable(category="Gauge", enumeration="clockwise,cclockwise")]
    
    /**
     * The orientation of the scale. It can be clockwise (<code>"clockwise"</code>)
     * or counterclockwise (<code>"cclockwise"</code>). 
     * 
     * @default "clockwise"
     */  
    public function get orientation():String
    {
      return _scaleParam.orientation;
    }

    /**
     * @private 
     */
    public function set orientation(value:String):void
    {
      _scaleParam.orientation = value;
      _scaleParam.dirty = true;
      invalidateProperties();
    }
    
    /**
     * @private
     */
    protected override function commitProperties():void
    {
      if (_trackParam.dirty && track != null && getStyle("showTrack") == true) {
        track.minimum = _trackParam.minimum;
        track.maximum = _trackParam.maximum;
        _trackParam.dirty = false; 
      }      
      if (_scaleParam.dirty && scale != null /*&& getStyle("showScale") == true*/) {
        scale.minimum = _scaleParam.minimum;
        scale.maximum = _scaleParam.maximum;        
        scale.majorTickInterval = _scaleParam.majorTickInterval;
        scale.orientation = _scaleParam.orientation;
        scale.startAngle = _scaleParam.startAngle;
        scale.endAngle = _scaleParam.endAngle;
        if (scale is LinearScale) {
          LinearScale(scale).minorTickInterval = _scaleParam.minorTickInterval;
          LinearScale(scale).snapInterval = _scaleParam.snapInterval;
        }
        _scaleParam.dirty = false;
      }
      super.commitProperties();
    }

    /**
     * @private 
     */     
    private function changeListener(e:Event):void
    {
      _value = valueRenderer.value as Number;  
    }
    
  }
}
